﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities;

namespace Geometry
{
   ///////////////////////////////////////////////////////////////////////////
   /// <summary>
   /// An element in an IdealHeightMap, possibly subdivided into smaller
   /// triangles
   /// </summary>
   public class IdealTriangle
   {
      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Constructor. The vertices that are supplied are cached (not copied)
      /// so the caller should allocate copies if necessary. This saves extra
      /// allocations that may not be needed.
      /// Assumes that the vertices are correct (error checking takes too long here)
      /// </summary>
      /// <param name="map">The map that owns this triangle</param>
      /// <param name="left">The vertex on the left (would be part of a left child)</param>
      /// <param name="apex">The vertex at the apex</param>
      /// <param name="right">The vertex on the right (would be part of a right child)</param>
      public IdealTriangle(IdealHeightMap map, IdealVertex left, IdealVertex apex, IdealVertex right)
      {
         map_ = map;

         vertices_.Add(left);
         vertices_.Add(apex);
         vertices_.Add(right);

         // Work out the height of the middle of the hypotenuse, so that we
         // can quickly decide whether to split or merge
         UInt16 twoX = (UInt16)(left.x + right.x);
         UInt16 twoY = (UInt16)(left.y + right.y);

         // If the mid-point of the hypotenuse falls between two height points, we can't
         // split this. Assuming all is working correctly, this will only happen when
         // the triangle is the size of the grid. We use NaN for the height in this case
         UInt16 x = (UInt16)(twoX / 2);
         UInt16 y = (UInt16)(twoY / 2);
         float height = (0 == (twoX % 1)) ? map_.getHeight(x, y) : float.NaN;
         midPoint_ = new IdealVertex(x, y, height);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Split this triangle
      /// </summary>
      internal void split()
      {
         if ((null != base_) && (base_.base_ != this))
         {
            // We are a left/right neighbour of our base, so we cannot split
            // once. we must make a diamond by recursing splitting our base
            // until we get to a diamond or the edge of the mesh
            base_.split();
         }

         // Now that we know that we are either the edge of the map, or we
         // are the base of our base. It is safe to split them both

         // Split ourselves
         childLeft_ = new IdealTriangle(map_, vertices_[vertexApex_], midPoint_, vertices_[vertexLeft_]);
         childLeft_.parent_ = this;
         childLeft_.base_ = left_;
         childRight_ = new IdealTriangle(map_, vertices_[vertexRight_], midPoint_, vertices_[vertexApex_]);
         childRight_.parent_ = this;
         childRight_.base_ = right_;

         if (null != base_)
         {
            // Split the base
            base_.childLeft_ = new IdealTriangle(map_, base_.vertices_[vertexApex_], midPoint_, base_.vertices_[vertexLeft_]);
            base_.childLeft_.parent_ = base_;
            base_.childLeft_.base_ = left_;
            base_.childRight_ = new IdealTriangle(map_, base_.vertices_[vertexRight_], midPoint_, base_.vertices_[vertexApex_]);
            base_.childRight_.parent_ = base_;
            base_.childRight_.base_ = right_;

            // Link the children that lie next to each other
            childLeft_.right_ = base_.childRight_.left_;
            childRight_.left_ = base_.childLeft_.right_;
         }
         else
         {
            // There is no base, we are at the edge of the grid
            childLeft_.right_ = childRight_.left_ = null;
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Do we need to split?
      /// </summary>
      public void checkSplit(DateTime timeLimit)
      {
         // If we are already at the resolution of the map, then we cannot split
         if (float.IsNaN(midPoint_.z))
         {
            return;
         }
         IdealHeightMap.CheckNeedsSplit splitCheck = map_.needSplitChecker;
         if (null != splitCheck)
         {
            // If there are no children, then we are the the triangle that will be
            // rendered, so check the error in our hypotenuse
            if ((null == childLeft_) && (DateTime.Now < timeLimit) &&
               splitCheck(this))
            {
               // The rendered height of the hypotenuse is too far away from the true height,
               // we need to split this triangle
               split();
            }

            // If there are children (may have just created them), then we
            // check those now
            if ((null != childLeft_) && (DateTime.Now < timeLimit))
            {
               // We may run out of time part-way through but we do both
               // children to ensure that the right-hand one always
               // gets checked. It's too expensive on memory to make this
               // more subtle. If something gets split, then it will sort
               // itself out for the next frame
               childLeft_.checkSplit(DateTime.MaxValue);
            }
            if ((null != childRight_) && (DateTime.Now < timeLimit))
            {
               childRight_.checkSplit(DateTime.MaxValue);
            }
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Can we merge?
      /// </summary>
      public void checkMerge(DateTime timeLimit)
      {
         // If we have no parent, then we cannot merge
         if (null == parent_)
         {
            return;
         }

         while (DateTime.Now < timeLimit)
         {
            ;
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public IdealTriangle neighbourBase
      {
         get { return base_; }
         set { base_ = value; }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public IdealTriangle childLeft { get { return childLeft_; } }
      public IdealTriangle childRight { get { return childRight_; } }
      public IdealVertex vertexLeft { get { return vertices_[vertexLeft_]; } }
      public IdealVertex vertexRight { get { return vertices_[vertexRight_]; } }
      public IdealVertex vertexApex { get { return vertices_[vertexApex_]; } }

      ////////////////////////////////////////////////////////////////////////
      //
      public override string ToString()
      {
         return string.Format("<{0} ^{1} {2}>", vertexLeft, vertexApex, vertexRight);
      }

      ////////////////////////////////////////////////////////////////////////
      //
      private static Logger logger_ = new Logger();

      private readonly IdealHeightMap map_;

      private List<IdealVertex> vertices_ = new List<IdealVertex>(3);
      private const int vertexLeft_ = 0;
      private const int vertexApex_ = 1;
      private const int vertexRight_ = 2;

      // Middle of the hypotenuse
      private readonly IdealVertex midPoint_; // z == Nan if we are at minimum size and cannot split
      
      private IdealTriangle parent_;
      private IdealTriangle left_;
      private IdealTriangle right_;
      private IdealTriangle base_;

      private IdealTriangle childLeft_;
      private IdealTriangle childRight_;
   }
}
